package com.ec.auth.datasource;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 动态数据源管理 继承AbstractRoutingDataSource
 * 就是多数据源(主从数据库)
 * 主jdbc:mysql://localhost:3306/ 从jdbc:mysql://localhost:3308/
 * +动态连接数据库jdbc:mysql://localhost:3306/db1 jdbc:mysql://localhost:3306/db2
 * jdbc:mysql://localhost:3308/db3 jdbc:mysql://localhost:3308/db4
 * 有对应库的连接对象 existDataSource 就直接连
 * 没有就determineCurrentLookupKey 然后就更新setTargetDataSources
 * DynamicRoutingDataSource 类继承自 AbstractRoutingDataSource，
 * 它是一个动态路由数据源的实现
 * 通过 addDataSource 方法可以添加数据源，
 * existDataSource 方法可以检查数据源是否存在，dataSource 方法可以创建数据源。
 * determineCurrentLookupKey 方法用于确定当前使用的数据源
 * @author xxxx
 */
@Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

    // 这是一个 Map 对象，用于存储目标数据源
    private static Map<Object, Object> targetTargetDataSources = new ConcurrentHashMap<>();


    /**
     * 这个方法重写了父类的方法。它的作用是确定当前要使用的数据源键。具体来说，
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        // 它从 DynamicDataSourceContextHolder 获取数据源键
        // 每次连接数据库，都会去设置数据源
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }

    /**
     * 这个方法用于设置目标数据源
     * 设置targetDataSources并记录数据源（这里可以记录每个数据源的最近使用时间，可以做删除不经常使用的数据源）
     * @param targetDataSources
     */
    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        // 它首先调用父类的 setTargetDataSources 方法来设置数据源，
        super.setTargetDataSources(targetDataSources);
        // 然后调用 afterPropertiesSet 方法进行一些后续的设置。
        super.afterPropertiesSet();
        // 最后，它将设置的数据源保存到 targetTargetDataSources 中
        targetTargetDataSources = targetDataSources;
    }


    /**
     * 这个方法用于添加数据源。
     * @param tenant
     * @param dataSourceProperties
     */
    public void addDataSource(String tenant, Map<String, Object> dataSourceProperties) {
        targetTargetDataSources.put(tenant, dataSource(dataSourceProperties));
        // 它将给定的租户和数据源属性放入 targetTargetDataSources 中，
        // 并更新数据源。
        super.setTargetDataSources(targetTargetDataSources);
        afterPropertiesSet();
    }

    /**
     * 判断是否存在数据源，存在直接取
     * @param tenant
     * @return
     */
    public boolean existDataSource(String tenant) {
        return targetTargetDataSources.containsKey(tenant);
    }

    /**
     * 这个方法用于组装数据源。它尝试使用给定的数据源属性创建一个 DruidDataSource，并在出现异常时记录错误并抛出运行时异常
     * @param dataSourceProperties
     * @return
     */
    public DataSource dataSource(Map<String, Object> dataSourceProperties) {
        DataSource dataSource;
        try {
            dataSource = DruidDataSourceFactory.createDataSource(dataSourceProperties);
        } catch (Exception e) {
            log.error("dataSource: {}", e);
            throw new RuntimeException();
        }
        return dataSource;
    }
}
